#include "stdafx.h"
#include "ConsoleManager.h"
#include <iostream>
#include <string>
#include <conio.h>

namespace MiniBasic
{
    const COORD ConsoleManager::USE_CURRENT_POS = {-1, -1};

    ConsoleManager::ConsoleManager(bool keepConsoleOpen, HANDLE hIn, HANDLE hOut) : hStdIn(hIn), hStdOut(hOut), creationFlags(0)
    {
        if(!GetConsoleWindow())
        {
            if(!keepConsoleOpen)
            {
                creationFlags |= CREATED_CONSOLE;
            }
            // initialize console and CRT standard files
            if(!AllocConsole())
            {
                throw HRESULT_FROM_WIN32(GetLastError());
            }
            creationFlags |= (GetConsoleOutputCP() << 16);
            SetConsoleOutputCP(850);
            freopen("CONIN$", "r", stdin);
            freopen("CONOUT$", "w", stdout);
            freopen("CONOUT$", "w", stderr);
        }
        DWORD wrote;
        if(!hStdIn)
        {
            hStdIn = GetStdHandle(STD_INPUT_HANDLE);
            INPUT_RECORD rec;
            if(!PeekConsoleInput(hStdIn, &rec, 0, &wrote))
            {
                hStdIn = CreateFileW(L"CONIN$", GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
                creationFlags |= CREATED_INPUT;
            }
        }
        if(!hStdOut)
        {
            hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
            if(!WriteConsoleW(hStdOut, L"", 0, &wrote, NULL))
            {
                hStdOut = CreateFileW(L"CONOUT$", GENERIC_READ | GENERIC_WRITE, FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
                creationFlags |= CREATED_OUTPUT;
            }
        }
        if(!hStdIn || hStdIn == INVALID_HANDLE_VALUE || !hStdOut || hStdOut == INVALID_HANDLE_VALUE)
        {
            // closing INVALID_HANDLE_VALUE is fine
            if(hStdIn && (creationFlags & CREATED_INPUT))
            {
                CloseHandle(hStdIn);
            }
            if(hStdOut && (creationFlags & CREATED_OUTPUT))
            {
                CloseHandle(hStdOut);
            }
            if(creationFlags & CREATED_CONSOLE)
            {
                FreeConsole();
            }
            throw HRESULT_FROM_WIN32(ERROR_BAD_ENVIRONMENT);
        }
        GetConsoleScreenBufferInfo(hStdOut, &originalSettings);
    }

    ConsoleManager::~ConsoleManager()
    {
        if(creationFlags & CREATED_INPUT)
        {
            CloseHandle(hStdIn);
        }
        if(creationFlags & CREATED_OUTPUT)
        {
            CloseHandle(hStdOut);
        }
        if(creationFlags & CREATED_CONSOLE)
        {
            FreeConsole();
        }
        else
        {
            SetConsoleOutputCP(creationFlags >> 16);
        }
    }

    BOOL ConsoleManager::FloodAttribute(WORD wAttribute, COORD startLocation, DWORD numToWrite)
    {
        if((startLocation.X  == USE_CURRENT_POS.X) && (startLocation.Y  == USE_CURRENT_POS.Y))
        {
            CONSOLE_SCREEN_BUFFER_INFO cbsi;
            if(GetConsoleScreenBufferInfo(hStdOut, &cbsi))
            {
                startLocation = cbsi.dwCursorPosition;
            }
            else return FALSE;
        }
        DWORD wrote;
        return FillConsoleOutputAttribute(
            hStdOut, 
            wAttribute,
            numToWrite,
            startLocation,
            &wrote);
    }

    BOOL ConsoleManager::ChangeCurrentAttributes(WORD newAttributes)
    {
        return SetConsoleTextAttribute(hStdOut, newAttributes);
    }

    BOOL ConsoleManager::FloodChar(WCHAR character, COORD startLocation, DWORD numToWrite)
    {
        if((startLocation.X  == USE_CURRENT_POS.X) && (startLocation.Y  == USE_CURRENT_POS.Y))
        {
            CONSOLE_SCREEN_BUFFER_INFO cbsi;
            if(GetConsoleScreenBufferInfo(hStdOut, &cbsi))
            {
                startLocation = cbsi.dwCursorPosition;
            }
            else return FALSE;
        }
        DWORD wrote;
        return FillConsoleOutputCharacterW(
            hStdOut,
            character,
            numToWrite,
            startLocation,
            &wrote);
    }

    BOOL ConsoleManager::Clear()
    {
        CONSOLE_SCREEN_BUFFER_INFO cbsi;
        if(GetConsoleScreenBufferInfo(hStdOut, &cbsi))
        {
            COORD origin = {0, 0};
            return FloodAttribute(cbsi.wAttributes, origin) && FloodChar(L' ', origin)
                && MoveCursor(origin) && ToggleCursor(TRUE);
        }
        return FALSE;
    }

    BOOL ConsoleManager::MoveCursor(COORD newPosition)
    {
        return SetConsoleCursorPosition(hStdOut, newPosition);
    }

    BOOL ConsoleManager::ToggleCursor(BOOL bOn)
    {
        CONSOLE_CURSOR_INFO cci = {0};
        cci.bVisible = bOn;
        cci.dwSize = bOn ? 25 : 1; // 25 is the command prompt default
        return SetConsoleCursorInfo(hStdOut, &cci);
    }

    BOOL ConsoleManager::ResizeWindow(COORD dims)
    {
        BOOL bRet = FALSE;
        CONSOLE_SCREEN_BUFFER_INFO cbsi;
        GetConsoleScreenBufferInfo(hStdOut, &cbsi);
        SMALL_RECT sr = {0, 0, dims.X - 1, dims.Y - 1};
        if(dims.X > cbsi.dwSize.X)
        {
            dims.Y = std::max<SHORT>(dims.Y, cbsi.dwSize.Y);
            bRet = SetConsoleScreenBufferSize(hStdOut, dims);
            if(bRet)
            {
                bRet = SetConsoleWindowInfo(hStdOut, TRUE, &sr);
            }
        }
        else
        {
            bRet = SetConsoleWindowInfo(hStdOut, TRUE, &sr);
            if(bRet)
            {
                dims.Y = std::max<SHORT>(dims.Y, cbsi.dwSize.Y);
                bRet = SetConsoleScreenBufferSize(hStdOut, dims);
            }
        }
        return bRet;
    }

    void ConsoleManager::RestoreToOriginal()
    {
        const SMALL_RECT& sr = originalSettings.srWindow;
        COORD cd = {sr.Right - sr.Left, sr.Bottom - sr.Top};
        ResizeWindow(cd);
        DWORD totalSpaces = static_cast<DWORD>(originalSettings.dwSize.X) * originalSettings.dwSize.Y;
        cd.X = 0;
        cd.Y = 0;
        FloodAttribute(originalSettings.wAttributes, cd, totalSpaces);
        ChangeCurrentAttributes(originalSettings.wAttributes);
        ToggleCursor(TRUE);
    }

    void ConsoleManager::OutputText(const std::string& text) const
    {
        std::cout << text << std::flush;
    }

    mbint ConsoleManager::GetKey() const
    {
        mbint input = _getch();
        // if an extended key was pressed, get the real value
        if(input == 0 || input == 0xE0)
        {
            // add 166 so codes between 59 and 88 are mapped to 225 to 254
            // as per the reference implementation
            input = _getch();
            if(input >= 59 && input <= 88)
            {
                input += 166;
            }
        }
        return input;
    }

    bool ConsoleManager::GetFromUserWithEnter(std::string& value) const
    {
        void* result = std::getline(std::cin, value);
        if(!result)
        {
            std::cin.clear();
            std::cin.ignore(MAXLONG);
        }
        // if it failed for eof, return success
        return result || std::cin.eof();
    }
}
